#include "cexceptions/.Throwable.h"

static const char * _throwableDumpFormat="Exception<%i> \"%s\" with code %i in file \"%s\" on line %i";
static const char _causedBy[13]="\nCaused by:\n";
static const size_t _causedByLength=12;

Throwable_t Throwable_allocate(size_t count){
  Throwable_t this=calloc(count, sizeof(struct _Throwable));
  assert(this && "Failed to allocate memory for Throwable");
  return this;
}
Throwable_t Throwable_construct(Throwable_t this, int type, char * message, int code, Throwable_t previous, char * file, int line){
  assert(this && "NULL given as this-argument");
  assert(type && "Exception type must be non-zero");
  char * _message=NULL;
  if(message){
    size_t messageLength=strlen(message);
    _message=malloc(messageLength+1);
    assert(_message && "Failed to allocate memory for message");
    strncpy(_message, message, messageLength);
    _message[messageLength]='\0';
  }
  Throwable_t _previous=NULL;
  if(previous){
    _previous=malloc(sizeof(struct _Throwable));
    assert(_previous && "Failed to allocate memory for previous");
    Throwable_construct(_previous, previous->type, previous->message, previous->code, previous->previous, previous->file, previous->line);
  }
  char * _file=NULL;
  if(file){
    size_t fileLength=strlen(file);
    _file=malloc(fileLength+1);
    assert(_file && "Failed to allocate memory for file");
    strncpy(_file, file, fileLength);
    _file[fileLength]='\0';
  }
  *this=(struct _Throwable){.type=type, .message=_message, .code=code, .previous=_previous, .file=_file, .line=line};
  return this;
}
Throwable_t Throwable_clone(Throwable_t this, Throwable_t origin){
  assert(this && "NULL given as this-argument");
  assert(origin && "NULL given as origin-argument");
  if(this!=origin){
    Throwable_construct(this, origin->type, origin->message, origin->code, origin->previous, origin->file, origin->line);
  }
  return this;
}
Throwable_t Throwable_destruct(Throwable_t this){
  if(this){
    if(this->previous){
      Throwable_destruct(this->previous);
      free(this->previous);
    }
    if(this->message){
      free(this->message);
    }
    if(this->file){
      free(this->file);
    }
    *this=(struct _Throwable){0};
  }
  return this;
}
int Throwable_getType(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->type;
}
char * Throwable_getMessage(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->message;
}
int Throwable_getCode(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->code;
}
Throwable_t Throwable_getPrevious(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->previous;
}
char * Throwable_getFile(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->file;
}
int Throwable_getLine(Throwable_t this){
  if(!this){
    throw(THROWABLE_RUNTIME_ERROR, "NULL given as this-argument", 1, NULL);
  }
  return this->line;
}

int _Throwable_printf(FILE *stream, const struct printf_info *info, const void *const *args){
  Throwable_t const exceptionPtr=*(Throwable_t *)args[0];
  size_t bufferLength=1+snprintf(
    NULL,
    0,
    _throwableDumpFormat,
    exceptionPtr->type,
    exceptionPtr->message,
    exceptionPtr->code,
    exceptionPtr->file,
    exceptionPtr->line
  );
  for(Throwable_t iterator=exceptionPtr->previous;iterator;iterator=iterator->previous){
    bufferLength+=_causedByLength+snprintf(
      NULL,
      0,
      _throwableDumpFormat,
      iterator->type,
      iterator->message,
      iterator->code,
      iterator->file,
      iterator->line
    );
  }
  char * buffer=malloc(sizeof(char)*(bufferLength));
  size_t bufferOffset=sprintf(
    buffer,
    _throwableDumpFormat,
    exceptionPtr->type,
    exceptionPtr->message,
    exceptionPtr->code,
    exceptionPtr->file,
    exceptionPtr->line
  );
  for(Throwable_t iterator=exceptionPtr->previous;iterator;iterator=iterator->previous){
    bufferOffset+=sprintf(
      buffer+bufferOffset,
      _causedBy
    );
    bufferOffset+=sprintf(
      buffer+bufferOffset,
      _throwableDumpFormat,
      iterator->type,
      iterator->message,
      iterator->code,
      iterator->file,
      iterator->line
    );
  }
  buffer[bufferOffset]='\0';
  if(bufferOffset == -1){
    return -1;
  }
  size_t length=fprintf(stream, "%*s", (info->left ? -info->width : info->width), buffer);
  free (buffer);
  return length;
}

int _Throwable_printf_arginfo(const struct printf_info *info, size_t n, int *argtypes, int *size){
	(void)(info);
  if(n>0){
    argtypes[0]=PA_POINTER;
    size[0]=sizeof(Throwable_t);
  }
  return 1;
}

__attribute__((constructor)) void Throwable_register_printf_specifier (){
  register_printf_specifier('T', _Throwable_printf, _Throwable_printf_arginfo);
}

__attribute__((destructor)) void Throwable_unregister_printf_specifier(){
  register_printf_specifier('T', NULL, NULL);
}
